aboutsummaryrefslogtreecommitdiff
path: root/pages/anime/[...id].js
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-04-25 16:29:00 +0700
committerGitHub <[email protected]>2023-04-25 16:29:00 +0700
commit9b524cd5312dc01f955046f62cd40da26160a327 (patch)
tree9f1d172b60d482dea8268742ffce7de1f7aea9ca /pages/anime/[...id].js
parentUpdate [...id].js (diff)
parentMerge branch 'main' into pre-production (diff)
downloadmoopa-9b524cd5312dc01f955046f62cd40da26160a327.tar.xz
moopa-9b524cd5312dc01f955046f62cd40da26160a327.zip
Merge pull request #1 from DevanAbinaya/pre-production
Merge branch pre-production to main
Diffstat (limited to 'pages/anime/[...id].js')
-rw-r--r--pages/anime/[...id].js1064
1 files changed, 577 insertions, 487 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js
index eb562cc..cf70c62 100644
--- a/pages/anime/[...id].js
+++ b/pages/anime/[...id].js
@@ -1,218 +1,463 @@
-import React, { useEffect, useState } from "react";
-import { META } from "@consumet/extensions";
+import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
+import "react-loading-skeleton/dist/skeleton.css";
-import Link from "next/link";
-import Layout from "../../components/layout";
-import Head from "next/head";
+import { ClockIcon, HeartIcon } from "@heroicons/react/20/solid";
+import {
+ TvIcon,
+ ArrowTrendingUpIcon,
+ RectangleStackIcon,
+} from "@heroicons/react/24/outline";
-import { closestMatch } from "closest-match";
+import Head from "next/head";
+import Image from "next/image";
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+import Layout from "../../components/layout";
+import Link from "next/link";
import Content from "../../components/hero/content";
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "../api/auth/[...nextauth]";
-import Image from "next/image";
+import { useSession } from "next-auth/react";
+
+const query = `
+ query ($username: String, $status: MediaListStatus) {
+ MediaListCollection(userName: $username, type: ANIME, status: $status, sort: SCORE_DESC) {
+ user {
+ id
+ name
+ about (asHtml: true)
+ createdAt
+ avatar {
+ large
+ }
+ statistics {
+ anime {
+ count
+ episodesWatched
+ meanScore
+ minutesWatched
+ }
+ }
+ bannerImage
+ mediaListOptions {
+ animeList {
+ sectionOrder
+ }
+ }
+ }
+ lists {
+ status
+ name
+ entries {
+ id
+ mediaId
+ status
+ progress
+ score
+ media {
+ id
+ status
+ title {
+ english
+ romaji
+ }
+ episodes
+ coverImage {
+ large
+ }
+ }
+ }
+ }
+ }
+ }
+ `;
+
+const infoQuery = `query ($id: Int) {
+ Media(id: $id) {
+ id
+ type
+ title {
+ romaji
+ english
+ native
+ }
+ coverImage {
+ extraLarge
+ large
+ color
+ }
+ bannerImage
+ description
+ episodes
+ nextAiringEpisode {
+ episode
+ airingAt
+ }
+ averageScore
+ popularity
+ status
+ startDate {
+ year
+ }
+ duration
+ genres
+ relations {
+ edges {
+ relationType
+ node {
+ id
+ type
+ status
+ title {
+ romaji
+ english
+ userPreferred
+ }
+ coverImage {
+ extraLarge
+ large
+ color
+ }
+ }
+ }
+ }
+ recommendations {
+ nodes {
+ mediaRecommendation {
+ id
+ title {
+ romaji
+ }
+ coverImage {
+ extraLarge
+ large
+ }
+ }
+ }
+ }
+ }
+}`;
+
+export default function Info() {
+ const { data: session, status } = useSession();
+ const [data, setData] = useState(null);
+ const [info, setInfo] = useState(null);
+ const [episode, setEpisode] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [progress, setProgress] = useState(null);
+ const [statuses, setStatuses] = useState(null);
+ const [stall, setStall] = useState(false);
+ const [color, setColor] = useState(null);
-export default function Himitsu({
- info,
- color,
- episodeList,
- episode1,
- sessions,
- progress,
- status,
- lastPlayed,
- stall,
-}) {
- const [showText, setShowtext] = useState(false);
- const [load, setLoad] = useState(true);
const [showAll, setShowAll] = useState(false);
- const [time, setTime] = useState(0);
- const episode = episodeList;
- const epi1 = episode1;
+ const [time, setTime] = useState(0);
+ const { id } = useRouter().query;
- const maxItems = 3;
+ const rec = info?.recommendations?.nodes.map(
+ (data) => data.mediaRecommendation
+ );
- const nextAir = info.nextAiringEpisode;
- // console.log(time);
+ // console.log(rec);
+ // console.log(info);
useEffect(() => {
- if (nextAir) {
- setTime(convertSecondsToTime(nextAir.timeUntilAiring));
- }
-
- function getBrightness(color) {
- const rgb = color.match(/\d+/g);
- return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000;
- }
+ const defaultState = {
+ data: null,
+ info: null,
+ episode: null,
+ loading: true,
+ statuses: null,
+ progress: null,
+ };
- // set the text color based on the background color
- function setTextColor(element) {
- const backgroundColor = getComputedStyle(element).backgroundColor;
- const brightness = getBrightness(backgroundColor);
- if (brightness < 128) {
- element.style.color = "#fff"; // white
+ // Reset all state variables to their default values
+ Object.keys(defaultState).forEach((key) => {
+ const value = defaultState[key];
+ if (Array.isArray(value)) {
+ value.length
+ ? eval(
+ `set${
+ key.charAt(0).toUpperCase() + key.slice(1)
+ }(${JSON.stringify(value)})`
+ )
+ : eval(`set${key.charAt(0).toUpperCase() + key.slice(1)}([])`);
} else {
- element.style.color = "#000"; // black
+ eval(
+ `set${key.charAt(0).toUpperCase() + key.slice(1)}(${JSON.stringify(
+ value
+ )})`
+ );
}
- }
-
- const elements = document.querySelectorAll(".dynamic-text");
- elements.forEach((element) => {
- setTextColor(element);
});
+ async function fetchData() {
+ if (id) {
+ setLoading(false);
+ try {
+ const [res, info] = await Promise.all([
+ fetch(`https://api.moopa.my.id/meta/anilist/info/${id?.[0]}`),
+ fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ query: infoQuery,
+ variables: {
+ id: id?.[0],
+ },
+ }),
+ }),
+ ]);
+ const data = await res.json();
+ const infos = await info.json();
+ setInfo(infos.data.Media);
+
+ const textColor = setTxtColor(infos.data.Media.coverImage?.color);
+
+ if (!data || data.episodes.length === 0) {
+ const res = await fetch(
+ `https://api.consumet.org/meta/anilist/info/${id[0]}?provider=9anime`
+ );
+ const datas = await res.json();
+ setColor({
+ backgroundColor: `${data?.color || "#ffff"}`,
+ color: textColor,
+ });
+ setStall(true);
+ setEpisode(datas.episodes);
+ } else {
+ setEpisode(data.episodes);
+ }
+
+ setColor({
+ backgroundColor: `${data?.color || "#ffff"}`,
+ color: textColor,
+ });
+
+ if (session?.user?.name) {
+ const response = await fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ query: query,
+ variables: {
+ username: session?.user?.name,
+ },
+ }),
+ });
+
+ const dat = await response.json();
+
+ const prog = dat.data.MediaListCollection;
+
+ const gat = prog.lists.map((item) => item.entries);
+ const git = gat.map((item) =>
+ item.find((item) => item.media.id === parseInt(data?.id))
+ );
+ const gut = git?.find(
+ (item) => item?.media.id === parseInt(data?.id)
+ );
+
+ if (gut) {
+ setProgress(gut?.progress);
+ if (gut.status === "CURRENT") {
+ setStatuses("Watching");
+ } else if (gut.status === "PLANNING") {
+ setStatuses("Planned to watch");
+ } else if (gut.status === "COMPLETED") {
+ setStatuses("Completed");
+ } else if (gut.status === "DROPPED") {
+ setStatuses("Dropped");
+ } else if (gut.status === "PAUSED") {
+ setStatuses("Paused");
+ } else if (gut.status === "REPEATING") {
+ setStatuses("Rewatching");
+ }
+ }
+ }
+
+ if (data.nextAiringEpisode) {
+ setTime(
+ convertSecondsToTime(data.nextAiringEpisode.timeUntilAiring)
+ );
+ }
- setLoad(false);
- }, [color, sessions, info.id]);
+ setData(data);
+ setLoading(true);
+ } catch (error) {
+ console.log(error);
+ setTimeout(() => {
+ window.location.reload();
+ }, 1000);
+ }
+ }
+ }
+ fetchData();
+ }, [id, session?.user?.name]);
return (
<>
<Head>
- <title>{info.title?.english || info.title.romaji}</title>
- <meta name="detail" content="Detail about the Anime" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <link rel="icon" href="/c.svg" />
+ <title>
+ {info
+ ? info?.title?.romaji || info?.title?.english
+ : "Retrieving Data..."}
+ </title>
</Head>
-
- <Layout navTop="text-white bg-primary md:pt-0 md:px-0 bg-slate bg-opacity-40">
- <div className="text static bg-primary flex w-screen flex-col justify-center pt-nav md:pt-1 pb-10">
- <div className="pointer-events-none absolute top-0 left-0">
- <div className="absolute bg-gradient-to-t w-screen z-20 top-0 md:h-[300px] h-[420px] from-10% from-primary to-transparent" />
- <img
- // ref={ref}
- src={info.cover || info.image}
- className="md:h-[300px] h-[420px] w-screen object-cover brightness-[60%]"
- />
- </div>
- {info ? (
- <div className="flex flex-col items-center gap-10">
- <div className="flex w-screen flex-col gap-10 md:w-[70%]">
- <div className="z-40 flex flex-col gap-10 px-5 pt-[7rem] md:flex-row lg:mt-[5rem] lg:px-0">
- <div className="flex gap-5 md:h-[250px] md:w-52">
- <div className="flex h-[200px] w-52 bg-[#dadada50] md:h-[250px] md:w-full">
- {info.image && (
- <>
- <div
- key={info.id}
- // src={info.image}
- className=""
- >
- <Image
- src={info.image}
- alt="image"
- width={500}
- height={500}
- draggable={false}
- className="object-cover h-[200px] w-[200px] md:h-[250px] shrink-0 bg-image shadow-md"
- />
- </div>
- </>
- )}
- </div>
-
- {/* MOBILE */}
- <div className="w-full grid place-items-stretch gap-3 lg:hidden ">
- <h1 className="shrink-0 text-xl font-semibold line-clamp-2">
- {info.title.romaji || info.title.english}
- </h1>
- <div className="flex w-[90%] flex-col gap-1">
- <div className="flex gap-2">
- <h1>Rate:</h1>
- <p className="font-bold">{info.rating}%</p>
- </div>
-
- <div className="flex w-[200px] gap-2">
- <h1>Format:</h1>
- <p>{info.type}</p>
+ <SkeletonTheme baseColor="#232329" highlightColor="#2a2a32">
+ <Layout navTop="text-white bg-primary md:pt-0 md:px-0 bg-slate bg-opacity-40 z-50">
+ <div className="w-screen min-h-screen relative flex flex-col items-center bg-primary gap-5">
+ <div className="bg-image w-screen">
+ <div className="bg-gradient-to-t from-primary from-10% to-transparent absolute h-[300px] w-screen z-10 inset-0" />
+ {info ? (
+ <Image
+ src={
+ info?.bannerImage ||
+ info?.coverImage?.extraLarge ||
+ info?.coverImage.large
+ }
+ alt="banner anime"
+ height={1000}
+ width={1000}
+ className="object-cover bg-image w-screen absolute top-0 left-0 h-[300px] brightness-[70%] z-0"
+ />
+ ) : (
+ <div className="bg-image w-screen absolute top-0 left-0 h-[300px]" />
+ )}
+ </div>
+ <div className="lg:w-[70%] md:pt-[10rem] z-30 flex flex-col gap-5">
+ {/* Mobile */}
+
+ <div className="md:hidden pt-5 w-screen px-5 flex flex-col">
+ <div className="h-[250px] flex flex-col gap-1 justify-center">
+ <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]">
+ {/* Yuru Campā–³ SEASON 2 */}
+ {info?.title?.romaji || info?.title?.english}
+ </h1>
+ <p
+ className="line-clamp-2 text-sm font-light antialiased w-[56%]"
+ dangerouslySetInnerHTML={{ __html: info?.description }}
+ />
+ <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]">
+ {info?.genres
+ ?.slice(
+ 0,
+ info?.genres?.length > 3 ? info?.genres?.length : 3
+ )
+ .map((item, index) => (
+ <span
+ key={index}
+ className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full"
+ // style={color}
+ >
+ <span className="">{item}</span>
+ {/* {index !== info?.genres?.length - 1 && (
+ <span className="w-[5px] h-[5px] ml-[6px] mb-[2px] inline-block rounded-full bg-white" />
+ )} */}
+ </span>
+ ))}
+ </div>
+ {info && (
+ <div className="flex items-center gap-5 pt-3 text-center">
+ <div className="flex items-center gap-2 text-center">
+ <div className="bg-action px-10 rounded-sm font-karla font-bold">
+ {statuses ? statuses : "Add to List"}
</div>
-
- <div className="flex gap-2">
- <h1>Status:</h1>
- <p>{info.status}</p>
+ <div className="h-6 w-6">
+ <HeartIcon />
</div>
-
- {/* {nextAir && (
- <div className="flex gap-2">
- <h1>Ep {nextAir.episode}:</h1>
- <p>{time}</p>
- </div>
- )} */}
- </div>
- <div className="flex">
- {epi1 && epi1[0] ? (
- <Link href={`/anime/watch/${epi1[0].id}/${info.id}`}>
- <h1 className="flex cursor-pointer items-center gap-2 px-1 py-2 font-bold text-[#ffffff]">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="13"
- height="12"
- fill="none"
- viewBox="0 0 250 289"
- >
- <path
- fill="#fff"
- d="M249.734 144.5l-249 143.761V.741l249 143.759z"
- ></path>
- </svg>{" "}
- WATCH
- </h1>
- </Link>
- ) : (
- <h1 className="pointer-events-none flex items-center gap-2 px-1 py-2 font-bold text-[#ffffffa5]">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="13"
- height="12"
- className="fill-[#ffffff8d]"
- viewBox="0 0 250 289"
- >
- <path d="M249.734 144.5l-249 143.761V.741l249 143.759z"></path>
- </svg>{" "}
- WATCH
- </h1>
- )}
</div>
</div>
+ )}
+ </div>
+ <div className="bg-secondary rounded-sm h-[30px]">
+ <div className="flex items-center justify-center h-full gap-10 p-2">
+ {info && info.status !== "NOT_YET_RELEASED" ? (
+ <>
+ <div className="flex-center gap-2">
+ <TvIcon className="w-5 h-5 text-action" />
+ <h4 className="font-karla">{info?.type}</h4>
+ </div>
+ <div className="flex-center gap-2">
+ <ArrowTrendingUpIcon className="w-5 h-5 text-action" />
+ <h4>{info?.averageScore}%</h4>
+ </div>
+ <div className="flex-center gap-2">
+ <RectangleStackIcon className="w-5 h-5 text-action" />
+ <h1>{info?.episodes} Episodes</h1>
+ </div>
+ </>
+ ) : (
+ <div>{info && "Not Yet Released"}</div>
+ )}
</div>
+ </div>
+ </div>
+
+ {/* PC */}
+ <div className="hidden md:flex gap-5 w-full flex-nowrap">
+ <div className="shrink-0 md:h-[250px] md:w-[180px] w-[115px] h-[164px] relative">
+ {info ? (
+ <>
+ <div className="bg-image md:h-[250px] md:w-[180px] w-[115px] h-[164px] bg-opacity-30 absolute backdrop-blur-lg z-10" />
+ <Image
+ src={
+ info.coverImage.extraLarge || info.coverImage.large
+ }
+ alt="poster anime"
+ height={700}
+ width={700}
+ className="object-cover md:h-[250px] md:w-[180px] w-[115px] h-[164px] z-20 absolute"
+ />
+ </>
+ ) : (
+ <Skeleton className="h-[250px] w-[180px]" />
+ )}
+ </div>
- {/* PC */}
- <div className="w-full flex-col gap-5 md:flex">
- <div className="hidden flex-col gap-5 lg:flex">
- <h1 className="text-4xl font-bold">
- {info.title?.english ||
- info.title.romaji ||
- info.title.native}
- </h1>
+ {/* PC */}
+ <div className="hidden md:flex w-full flex-col gap-5 px-3 h-[250px]">
+ <div className="flex flex-col gap-2">
+ <h1 className=" font-inter font-bold text-[36px] text-white line-clamp-1">
+ {info ? (
+ info?.title?.romaji || info?.title?.english
+ ) : (
+ <Skeleton width={450} />
+ )}
+ </h1>
+ {info ? (
<div className="flex gap-6">
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
style={color}
>
- {episode && episode.length} Episodes
+ {info?.episodes} Episodes
</div>
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
style={color}
>
- {info.releaseDate}
+ {info?.startDate?.year}
</div>
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
style={color}
>
- {info.rating}%
+ {info?.averageScore}%
</div>
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
style={color}
>
- {info.type}
+ {info?.type}
</div>
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
style={color}
>
- {info.status}
+ {info?.status}
</div>
<div
className={`dynamic-text rounded-md px-2 font-karla font-bold`}
@@ -220,390 +465,219 @@ export default function Himitsu({
>
Sub | EN
</div>
- {nextAir && (
+ {info && info.nextAiringEpisode && (
<div
className={`dynamic-text shadow-button rounded-md px-2 font-karla font-bold`}
style={color}
>
- Ep {nextAir.episode}: {time}
+ Ep {info.nextAiringEpisode.episode}: {time}
</div>
)}
</div>
- </div>
- <div
- className={`hidden h-[140px] transition-all duration-300 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`}
- >
- <p
- dangerouslySetInnerHTML={{ __html: info.description }}
- className="mr-5"
- />
- </div>
- <div className="lg:hidden text-sm text-txt">
- <p
- className={`${showText ? "" : "line-clamp-3"}`}
- dangerouslySetInnerHTML={{
- __html: info.description,
- }}
- />
- <button
- onClick={() => setShowtext(!showText)}
- className="font-rama font-bold text-white"
- >
- {showText ? " Show Less" : " Show More"}
- </button>
- </div>
+ ) : (
+ <Skeleton width={240} height={32} />
+ )}
</div>
+ {info ? (
+ <p
+ dangerouslySetInnerHTML={{ __html: info?.description }}
+ className="overflow-y-scroll scrollbar-thin pr-2 scrollbar-thumb-secondary scrollbar-thumb-rounded-lg h-[140px]"
+ />
+ ) : (
+ <Skeleton className="h-[130px]" />
+ )}
+ {/* <p>{data.description}</p> */}
</div>
+ </div>
- <div className="">
- <div className="flex gap-5 items-center">
+ <div>
+ <div className="flex gap-5 items-center">
+ {info && (
<div className="p-3 lg:p-0 text-[20px] md:text-2xl font-bold font-karla">
Relations
</div>
- {info.relations.length > maxItems && (
- <div
- className="cursor-pointer"
- onClick={() => setShowAll(!showAll)}
- >
- {showAll ? "show less" : "show more"}
- </div>
- )}
- </div>
- <div
- className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-10 rounded-xl`}
- >
- {info.relations &&
- info.relations
- .slice(0, showAll ? info.relations.length : maxItems)
- .map((relation, index) => {
+ )}
+ {info?.relations?.edges?.length > 3 && (
+ <div
+ className="cursor-pointer"
+ onClick={() => setShowAll(!showAll)}
+ >
+ {showAll ? "show less" : "show more"}
+ </div>
+ )}
+ </div>
+ <div
+ className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-4 rounded-xl`}
+ >
+ {info?.relations?.edges
+ ? info?.relations?.edges
+ .slice(0, showAll ? info?.relations?.edges.length : 3)
+ .map((r, index) => {
+ const rel = r.node;
return (
<Link
- key={relation.id}
+ key={rel.id}
href={
- relation.type === "TV" ||
- relation.type === "OVA" ||
- relation.type === "MOVIE" ||
- relation.type === "SPECIAL" ||
- relation.type === "ONA"
- ? `/anime/${relation.id}`
+ rel.type === "ANIME" ||
+ rel.type === "OVA" ||
+ rel.type === "MOVIE" ||
+ rel.type === "SPECIAL" ||
+ rel.type === "ONA"
+ ? `/anime/${rel.id}`
: `/manga/detail/id?aniId=${
- relation.id
+ rel.id
}&aniTitle=${encodeURIComponent(
- info.title?.english ||
- info.title.romaji ||
- info.title.native
+ info?.title?.english ||
+ info?.title.romaji ||
+ info?.title.native
)}`
}
className={`hover:scale-[1.02] hover:shadow-lg md:px-0 px-4 scale-100 transition-transform duration-200 ease-out w-full ${
- relation.type === "MUSIC"
+ rel.type === "MUSIC"
? "pointer-events-none"
: ""
}`}
>
<div
- key={relation.id}
+ key={rel.id}
className="w-full shrink h-[126px] bg-secondary flex rounded-md"
>
<div className="w-[90px] bg-image rounded-l-md shrink-0">
- <img
- src={relation.image}
- alt={relation.id}
+ <Image
+ src={
+ rel.coverImage.extraLarge ||
+ rel.coverImage.large
+ }
+ alt={rel.id}
+ height={500}
+ width={500}
className="object-cover h-full w-full shrink-0 rounded-l-md"
/>
</div>
<div className="h-full grid px-3 items-center">
<div className="text-action font-outfit font-bold">
- {relation.relationType}
+ {r.relationType}
</div>
<div className="font-outfit font-thin line-clamp-2">
- {relation.title.romaji}
+ {rel.title.userPreferred ||
+ rel.title.romaji}
</div>
- <div className={``}>{relation.type}</div>
+ <div className={``}>{rel.type}</div>
</div>
</div>
</Link>
);
- })}
- </div>
+ })
+ : [1, 2, 3].map((item) => (
+ <div key={item} className="w-full">
+ <Skeleton className="h-[126px]" />
+ </div>
+ ))}
</div>
-
- <div className="z-20 flex flex-col gap-10 p-3 lg:p-0">
- <div className="flex items-center md:gap-10 gap-7">
+ </div>
+ <div className="z-20 flex flex-col gap-10 p-3 lg:p-0">
+ <div className="flex items-center md:gap-10 gap-7">
+ {info && (
<h1 className="text-[20px] md:text-2xl font-bold font-karla">
Episodes
</h1>
- <div className="flex items-center rounded-md">
- <button
- // onClick={handleEnLang}
- className={
- // Lang?
- `w-14 p-1 rounded-l-md bg-secondary text-action shadow-action`
- // `w-14 p-1 rounded-l-md bg-[#17171b] text-[#404040]`
- }
- >
- EN
- </button>
- <div className="w-[1px] bg-white h-4" />
- <button
- // onClick={handleIdLang}
- className={
- // subIndo === null
- // ?
- `w-14 p-1 rounded-r-md bg-[#171717] text-[#404040] pointer-events-none`
- // : Lang
- // ? `w-14 p-1 rounded-r-md bg-[#171717] text-[#404040]`
- // : `w-14 p-1 rounded-r-md bg-[#212121]`
- }
- >
- ID
- </button>
- </div>
- {status && (
- <>
- <div className="font-karla relative group flex justify-center">
- {status}
- <span className="absolute bottom-8 shadow-lg invisible group-hover:visible transition-all opacity-0 group-hover:opacity-100 font-karla font-light bg-secondary p-1 px-2 rounded-lg">
- status
- </span>
+ )}
+ {info?.nextAiringEpisode && (
+ <div className="flex items-center gap-2">
+ <div className="flex items-center gap-4">
+ <h1>Next Ep :</h1>
+ <div
+ className="px-5 rounded-sm font-karla font-bold bg-white text-black"
+ // style={color}
+ >
+ {time}
</div>
- </>
- )}
- </div>
- <div className="flex h-[640px] flex-col gap-5 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]">
- {load ? (
- <p>Loading...</p>
- ) : episode ? (
- episode.map((episode, index) => {
- return (
- <div key={index} className="flex flex-col gap-3 px-2">
- <Link
- href={`/anime/watch/${episode.id}/${info.id}/${
- stall ? `9anime` : ""
- }`}
- className={`text-start text-sm md:text-lg ${
- episode.number <= progress
- ? "text-[#5f5f5f]"
- : "text-white"
- }`}
+ </div>
+ <div className="h-6 w-6">
+ <ClockIcon />
+ </div>
+ </div>
+ )}
+ {statuses && (
+ <>
+ <div className="hidden font-karla relative group md:flex justify-center">
+ {statuses}
+ <span className="absolute bottom-8 shadow-lg invisible group-hover:visible transition-all opacity-0 group-hover:opacity-100 font-karla font-light bg-secondary p-1 px-2 rounded-lg">
+ status
+ </span>
+ </div>
+ </>
+ )}
+ </div>
+ {loading ? (
+ data && (
+ <div className="flex h-[640px] flex-col gap-5 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]">
+ {episode ? (
+ episode.map((epi, index) => {
+ return (
+ <div
+ key={index}
+ className="flex flex-col gap-3 px-2"
>
- <p>Episode {episode.number}</p>
- {episode.title && (
- <p
- className={`text-xs md:text-sm ${
- episode.number <= progress
- ? "text-[#5f5f5f]"
- : "text-[#b1b1b1]"
- } italic`}
- >
- "{episode.title}"
- </p>
+ <Link
+ href={`/anime/watch/${epi.id}/${data.id}/${
+ stall ? `9anime` : ""
+ }`}
+ className={`text-start text-sm md:text-lg ${
+ progress && epi.number <= progress
+ ? "text-[#5f5f5f]"
+ : "text-white"
+ }`}
+ >
+ <p>Episode {epi.number}</p>
+ {epi.title && (
+ <p
+ className={`text-xs md:text-sm ${
+ progress && epi.number <= progress
+ ? "text-[#5f5f5f]"
+ : "text-[#b1b1b1]"
+ } italic`}
+ >
+ "{epi.title}"
+ </p>
+ )}
+ </Link>
+ {index !== episode?.length - 1 && (
+ <span className="h-[1px] bg-white" />
)}
- </Link>
- <div className="h-[1px] bg-white" />
- </div>
- );
- })
- ) : (
- <p>No Episodes Available</p>
- )}
+ </div>
+ );
+ })
+ ) : (
+ <p>No Episodes Available</p>
+ )}
+ </div>
+ )
+ ) : (
+ <div className="pb-10 w-full flex-center">
+ Loading Episodes...
</div>
- </div>
+ )}
</div>
+ </div>
+ {rec && (
<div className="w-screen md:w-[80%]">
<Content
ids="recommendAnime"
section="Recommendations"
- data={info.recommendations}
+ data={rec}
/>
</div>
- </div>
- ) : (
- <div className="flex h-screen flex-col items-center justify-center gap-10 pb-52 ">
- <h1 className="scale-150 font-roboto text-6xl text-red-400">
- 404
- </h1>
- <p className="text-4xl font-semibold">{`> Woops.. I think we don't have that Anime :(`}</p>
- <Link className="pt-10 text-2xl" href="/search/anime">
- Return to search
- </Link>
- </div>
- )}
- </div>
- </Layout>
+ )}
+ <div></div>
+ <div></div>
+ </div>
+ </Layout>
+ </SkeletonTheme>
</>
);
}
-export async function getServerSideProps(context) {
- context.res.setHeader(
- "Cache-Control",
- "public, s-maxage=10, stale-while-revalidate=59"
- );
- const session = await getServerSession(context.req, context.res, authOptions);
-
- const { id } = context.query;
- if (!id) {
- return {
- notFound: true,
- };
- }
-
- const provider = new META.Anilist();
-
- const [info, episodes] = await Promise.all([
- fetch(`https://api.moopa.my.id/meta/anilist/info/${id[0]}`).then((res) =>
- res.json()
- ),
- provider.fetchEpisodesListById(id[0]),
- ]);
-
- if (!info) {
- return {
- notFound: true,
- };
- }
-
- let episodeList = episodes;
- let stall = false;
-
- if (episodes.length === 0) {
- const res = await fetch(
- `https://api.consumet.org/meta/anilist/info/${id[0]}?provider=9anime`
- );
- const data = await res.json();
- episodeList = data.episodes;
- stall = true;
- }
-
- let progress = null;
- let status = null;
- let lastPlayed = null;
-
- if (session) {
- const response = await fetch("https://graphql.anilist.co/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- query: `
- query ($username: String, $status: MediaListStatus) {
- MediaListCollection(userName: $username, type: ANIME, status: $status, sort: SCORE_DESC) {
- user {
- id
- name
- about (asHtml: true)
- createdAt
- avatar {
- large
- }
- statistics {
- anime {
- count
- episodesWatched
- meanScore
- minutesWatched
- }
- }
- bannerImage
- mediaListOptions {
- animeList {
- sectionOrder
- }
- }
- }
- lists {
- status
- name
- entries {
- id
- mediaId
- status
- progress
- score
- media {
- id
- status
- title {
- english
- romaji
- }
- episodes
- coverImage {
- large
- }
- }
- }
- }
- }
- }
- `,
- variables: {
- username: session?.user.name,
- },
- }),
- });
-
- const dat = await response.json();
-
- // const resp = await fetch(`/api/get-user?userName=${session?.user.name}`);
- // const data = await resp.json();
-
- lastPlayed = session?.user?.recentWatch?.filter(
- (item) => item.title.romaji === info.title.romaji
- )[0]?.episode;
-
- const prog = dat.data.MediaListCollection;
-
- const gat = prog.lists.map((item) => item.entries);
- const git = gat.map((item) =>
- item.find((item) => item.media.id === parseInt(info.id))
- );
- const gut = git?.find((item) => item?.media.id === parseInt(info.id));
-
- if (gut) {
- progress = gut?.progress;
- if (gut.status === "CURRENT") {
- status = "Watching";
- } else if (gut.status === "PLANNING") {
- status = "Planned to watch";
- } else if (gut.status === "COMPLETED") {
- status = "Completed";
- } else if (gut.status === "DROPPED") {
- status = "Dropped";
- } else if (gut.status === "PAUSED") {
- status = "Paused";
- } else if (gut.status === "REPEATING") {
- status = "Rewatching";
- }
- }
- }
-
- const color = { backgroundColor: `${info.color || 'white'}` };
- const epi1 = episodes.filter((epi) => epi.number === 1);
- const title = info.title?.userPreferred || "No Title";
-
- return {
- props: {
- info: {
- ...info,
- title: {
- ...info.title,
- userPreferred: title,
- },
- },
- color,
- episodeList,
- episode1: epi1,
- sessions: session,
- progress: progress || null,
- status: status,
- lastPlayed: lastPlayed || null,
- stall,
- },
- };
-}
-
function convertSecondsToTime(sec) {
let days = Math.floor(sec / (3600 * 24));
let hours = Math.floor((sec % (3600 * 24)) / 3600);
@@ -625,3 +699,19 @@ function convertSecondsToTime(sec) {
return time.trim();
}
+
+function getBrightness(hexColor) {
+ if (!hexColor) {
+ return 200;
+ }
+ const rgb = hexColor
+ .match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i)
+ .slice(1)
+ .map((x) => parseInt(x, 16));
+ return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000;
+}
+
+function setTxtColor(hexColor) {
+ const brightness = getBrightness(hexColor);
+ return brightness < 150 ? "#fff" : "#000";
+}